package com.sam.lib.store.processor;

import com.sam.lib.store.config.Store;
import com.sam.lib.store.config.StoreField;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;

import static javax.lang.model.element.ElementKind.PACKAGE;


public class StoreProcessor extends AbstractProcessor {

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(Store.class.getCanonicalName());
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        long t = System.currentTimeMillis();
        for (Element element : roundEnv.getRootElements()) {//遍历所有的类
            if (element.getAnnotation(Store.class) != null) {
                Store store = element.getAnnotation(Store.class);
                String name = store.name();
                int mode = store.mode();
                buildClass(processingEnv, element, name, mode);
            }
        }

        System.out.printf(Locale.getDefault(), "StoreProcessor takes %4d millis  , scan files: %d%n", (System.currentTimeMillis() - t), roundEnv.getRootElements().size());
        return true;
    }

    private static void buildClass(ProcessingEnvironment processingEnv, Element target, String name, int mode) {
        //获取目标类名称和包名
        String clazzName = target.getSimpleName() + "Store";
        String packageName = getPackage(target).getQualifiedName().toString();
        try {
            //创建类
            TypeSpec spec = TypeSpec.classBuilder(clazzName)//类名称
                    .addModifiers(Modifier.PUBLIC)//设置类是public
                    .addFields(getFields(processingEnv, target, name, mode))
                    .addMethods(getMethods(processingEnv, target, name, mode))
                    .build();
            JavaFile javaFile = JavaFile.builder(packageName, spec).build();

            javaFile.writeTo(processingEnv.getFiler());//processingEnv是父类里的属性

        } catch (Exception e) {
            e.printStackTrace();
        }


    }

    public static List<MethodSpec> getMethods(ProcessingEnvironment processingEnv, Element target, String name, int mode) {
        System.out.println(target.getClass().getName());
        List<MethodSpec> methodSpecs = new ArrayList<>();

        for (Element child : target.getEnclosedElements()) {//遍历类的一级子元素
            StoreField storeField = child.getAnnotation(StoreField.class);
            if (storeField != null) {
                String key = storeField.value();
                if (key.length() > 0) {
                    methodSpecs.addAll(buildSetAndGetMethod(processingEnv, child, name, mode, key));
                }
            }
        }

        return methodSpecs;
    }

    public static List<MethodSpec> buildSetAndGetMethod(ProcessingEnvironment processingEnv, Element element, String name, int mode, String key) {

        List<MethodSpec> methodSpecs = new ArrayList<>();

        if (!element.asType().getKind().isPrimitive() && element.asType().getKind() != TypeKind.DECLARED) {
            return methodSpecs;//不支持的类型
        }

        String fieldName = element.getSimpleName().toString();
        ClassName storeManage = ClassName.get("com.sam.lib.store", "StoreManage");
        MethodSpec setMethodSpec = MethodSpec
                .methodBuilder("set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1))
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
//                .addParameter(ClassName.get(element.asType()), fieldName)
//                .addCode("$T.put($T.getContext(), $L, $S, $S, $N);", storeManage, storeManage, mode, name, key, fieldName)
                .addParameter(ClassName.get(element.asType()), "value")
                .addCode("$T.put($T.getContext(), FILE_MODE, FILE_NAME, KEY_$N, $N);", storeManage, storeManage, key.toUpperCase(), "value")
                .build();
        methodSpecs.add(setMethodSpec);

        MethodSpec getMethodSpec = MethodSpec
                .methodBuilder("get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1))
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
//                .addCode("return $T.$N($T.getContext(), $L, $S, $S);", storeManage, getMethodName(element), storeManage, mode, name, key)
                .addCode("return $T.$N($T.getContext(), FILE_MODE, FILE_NAME, KEY_$N);", storeManage, getMethodName(element), storeManage, key.toUpperCase())
                .returns(ClassName.get(element.asType()))
                .build();
        methodSpecs.add(getMethodSpec);

        return methodSpecs;
    }

    public static List<FieldSpec> getFields(ProcessingEnvironment processingEnv, Element target, String name, int mode) {
        List<FieldSpec> fieldSpecs = new ArrayList<>();
        fieldSpecs.add(FieldSpec.builder(ClassName.get("java.lang", "String"), "FILE_NAME", Modifier.STATIC, Modifier.PRIVATE)
                .initializer("$S", name)
                .build());
        fieldSpecs.add(FieldSpec.builder(TypeName.INT, "FILE_MODE", Modifier.STATIC, Modifier.PRIVATE)
                .initializer("$L", mode)
                .build());

        for (Element child : target.getEnclosedElements()) {//遍历类的一级子元素
            StoreField storeField = child.getAnnotation(StoreField.class);
            if (storeField != null) {
                String key = storeField.value();
                if (key.length() > 0) {
                    fieldSpecs.add(FieldSpec.builder(ClassName.get("java.lang", "String"), "KEY_" + key.toUpperCase(), Modifier.STATIC, Modifier.PRIVATE)
                            .initializer("$S", key)
                            .build());
                }
            }
        }


        return fieldSpecs;
    }

    public static PackageElement getPackage(Element element) {
        while (element.getKind() != PACKAGE) {
            element = element.getEnclosingElement();
        }
        return (PackageElement) element;
    }

    public static String getMethodName(Element element) {
        String type = element.asType().toString();
        switch (type) {
            case "java.lang.String":
                return "getString";
            case "java.lang.Integer":
            case "int":
                return "getInt";
            case "java.lang.Long":
            case "long":
                return "getLong";
            case "java.lang.Boolean":
            case "boolean":
                return "getBoolean";
            case "java.lang.Float":
            case "float":
                return "getFloat";
            case "java.util.Set<java.lang.String>":
                return "getStringSet";
        }

        throw new IllegalArgumentException(type + "type not allow ,annotation only support String、Set<String>、int、boolean、long、float。" + element.getEnclosingElement().toString());
    }
}